﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Reflection;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Reactive.Linq;

namespace QbservableCore.Serialization
{
    /// <summary>
    /// Represents a custom visitor or rewriter for expression trees.
    /// </summary>
    /// <remarks>
    /// initialize the translation of the entire expression tree
    /// </remarks>
    public class ExpressionToJson: ExpressionVisitor
    {
        

        // Temporary state (used because the visitor is route through its base class, so we can't change the arguments)
        internal JContainer JsonCurrent {get; set;}
        internal IQbservableConvention Convention {get; private set;}

        public ExpressionToJson(
            IQbservableConvention mapper,
            JContainer jsonCurrent = null)
        {
            Convention = mapper;
            JsonCurrent = jsonCurrent ?? new JObject();
        }

        #region ThrowIfNotSupported

        /// <summary>
        /// Throws exception if not supported.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <exception cref="System.NotSupportedException">The specified node type is not supported:  + node.NodeType</exception>
        private void ThrowIfNotSupported(Expression node)
        {
            switch (node.NodeType)
            {
                case ExpressionType.AddAssign:
                case ExpressionType.AddAssignChecked:
                case ExpressionType.AndAssign:
                case ExpressionType.AddChecked:
                case ExpressionType.Assign:
                case ExpressionType.Block:
                case ExpressionType.DebugInfo:
                case ExpressionType.Default:
                case ExpressionType.DivideAssign:
                case ExpressionType.Dynamic:
                case ExpressionType.ExclusiveOrAssign:
                case ExpressionType.Extension:
                case ExpressionType.Goto:
                case ExpressionType.Index:
                case ExpressionType.Invoke:
                case ExpressionType.Label:
                case ExpressionType.LeftShiftAssign:
                case ExpressionType.Loop:
                case ExpressionType.ModuloAssign:
                case ExpressionType.MultiplyAssign:
                case ExpressionType.MultiplyAssignChecked:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.NegateChecked:
                case ExpressionType.NewArrayBounds:
                case ExpressionType.OrAssign:
                case ExpressionType.PostDecrementAssign:
                case ExpressionType.PostIncrementAssign:
                case ExpressionType.PowerAssign:
                case ExpressionType.PreDecrementAssign:
                case ExpressionType.PreIncrementAssign:
                case ExpressionType.RightShiftAssign:
                case ExpressionType.RuntimeVariables:
                case ExpressionType.SubtractAssign:
                case ExpressionType.SubtractAssignChecked:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Switch:
                case ExpressionType.Throw:
                case ExpressionType.Try:
                case ExpressionType.Unbox:
                    throw new NotSupportedException("The specified node type is not supported: " + node.NodeType);
            }
        }

        #endregion // ThrowIfNotSupported
        
        /// <summary>
        /// Transforms the specified expression into json.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="mapper">The mapper.</param>
        /// <returns></returns>
        public static JContainer Transform(Expression node, IQbservableConvention mapper)
        {
            ExpressionToJson builder = new ExpressionToJson(mapper);
            builder.JsonCurrent = new JArray();
            builder.Build(node);
            return builder.JsonCurrent;
        }

        internal JContainer CreateAndTransform(Expression node, JContainer json = null)
        {
            ExpressionToJson builder = new ExpressionToJson(Convention, json);
            return builder.Build(node);
        }

        private JContainer Build(Expression node)
        {
            Contract.Requires(node != null);

            var exp = Visit(node);
            return JsonCurrent;
        }

        #region Visitors (override)

        #region Visit

        /// <summary>
        /// Dispatches the expression to one of the more specialized visit methods in this class.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        public override Expression Visit(Expression node)
        {
            ThrowIfNotSupported(node);

            Expression newNode = base.Visit(node); // will call other virtual methods
            return newNode;
        }

        #endregion // Visit

        #region VisitMethodCall

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MethodCallExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            MethodCall.BuildJson(node, this);
            return node;
        }

        #endregion // VisitMethodCall

        #region VisitLambda

        /// <summary>
        /// Visits the lambda.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            Lambda.ToJson<T>(node, this);
            return node;
        }

        #endregion // VisitLambda

        #region VisitParameter

        /// <summary>
        /// Visits the <see cref="T:System.Linq.Expressions.ParameterExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitParameter(ParameterExpression node)
        {
            Parameter.ToJson(node, this);
            return node;
        }

        #endregion // VisitParameter

        #region VisitUnary

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.UnaryExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitUnary(UnaryExpression node)
        {
            Unary.ToJson(node, this);
            return node;
        }

        #endregion // VisitUnary

        #region VisitBinary

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.BinaryExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            Binary.ToJson(node, this);
            return node;
        }

        #endregion // VisitBinary

        #region VisitConstant

        /// <summary>
        /// Visits the <see cref="T:System.Linq.Expressions.ConstantExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitConstant(ConstantExpression node)
        {
            Constant.ToJson(node, this);
            return node;
        }

        #endregion // VisitConstant

        #region VisitMember

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            throw new NotImplementedException();

            //if (node.Expression != null)
            //{
            //    Visit(node.Expression);
            //}
            //else
            //{
            //    _currentNode = null;
            //}
            //_currentNode = new PropertyOrFieldNode
            //{
            //    Target = _currentNode,
            //    MemberToken = _cache.GetOrAddMemberToken(node.Member),
            //    MemberName = node.Member.Name
            //};

            //return node;
        }

        #endregion // VisitMember

        #region VisitNew

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.NewExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitNew(NewExpression node)
        {
            throw new NotImplementedException();

            //ExpressionNodeBase[] args = new ExpressionNodeBase[node.Arguments.Count];
            //for (int index = 0; index < args.Length; index++)
            //{
            //    Visit(node.Arguments[index]);
            //    args[index] = _currentNode;
            //}
            //_currentNode = new NewInstanceNode
            //{
            //    Arguments = args,
            //    ConstructorToken = _cache.GetOrAddMethodToken(node.Constructor)
            //};

            //return node;
        }

        #endregion // VisitNew

        #region VisitMemberInit

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberInitExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            throw new NotImplementedException();

            //Visit(node.NewExpression);
            //NewInstanceNode newExpression = (NewInstanceNode)_currentNode;
            //MemberBindingNodeBase[] bindings = new MemberBindingNodeBase[node.Bindings.Count];
            //for (int index = 0; index < bindings.Length; index++)
            //{
            //    VisitMemberBinding(node.Bindings[index]);
            //    bindings[index] = (MemberBindingNodeBase)_rootBindingNode;
            //}

            //_currentNode = new MemberInitNode
            //{
            //    NewExpression = newExpression,
            //    Bindings = bindings
            //};

            //return node;
        }

        #endregion // VisitMemberInit

        #region VisitMemberBinding

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberBinding" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override MemberBinding VisitMemberBinding(MemberBinding node)
        {
            throw new NotImplementedException();

            //MemberBindingNodeBase binding = null;
            //switch (node.BindingType)
            //{
            //    case MemberBindingType.Assignment:
            //        MemberAssignment assignment = (MemberAssignment)node;
            //        Visit(assignment.Expression);
            //        MemberAssignmentNode assignmentNode = new MemberAssignmentNode
            //        {
            //            AssignedExpression = _currentNode
            //        };
            //        binding = assignmentNode;
            //        break;

            //    case MemberBindingType.ListBinding:
            //        MemberListBinding listBinding = (MemberListBinding)node;
            //        ElementInitNode[] initializers = GetInitializersNodes(listBinding.Initializers);
            //        ListBindingNode listBindingNode = new ListBindingNode
            //        {
            //            Initializers = initializers
            //        };
            //        binding = listBindingNode;
            //        break;

            //    case MemberBindingType.MemberBinding:
            //        MemberMemberBinding recursiveBinding = (MemberMemberBinding)node;
            //        MemberBindingNodeBase[] childrenBindings = new MemberBindingNodeBase[recursiveBinding.Bindings.Count];
            //        for (int index = 0; index < childrenBindings.Length; index++)
            //        {
            //            VisitMemberBinding(recursiveBinding.Bindings[0]);
            //            childrenBindings[index] = _rootBindingNode;
            //        }
            //        MemberMemberBindingNode memberBindingNode = new MemberMemberBindingNode
            //        {
            //            MemberBindings = childrenBindings
            //        };
            //        binding = memberBindingNode;
            //        break;
            //}

            //binding.BindingType = node.BindingType;
            //binding.MemberToken = _cache.GetOrAddMemberToken(node.Member);
            //binding.MemberName = node.Member.Name;
            //_rootBindingNode = binding;

            //return node;
        }

        #endregion // VisitMemberBinding

        #region VisitNewArray

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.NewArrayExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitNewArray(NewArrayExpression node)
        {
            throw new NotImplementedException();

            //ExpressionNodeBase[] initializers = new ExpressionNodeBase[node.Expressions.Count];
            //for (int index = 0; index < initializers.Length; index++)
            //{
            //    Visit(node.Expressions[index]);
            //    initializers[index] = _currentNode;
            //}
            //_currentNode = new NewArrayInitNode
            //{
            //    Initializers = initializers,
            //    TypeToken = _cache.GetTypeToken(node.Type.GetElementType())
            //};

            //return node;
        }

        #endregion // VisitNewArray

        #region VisitListInit

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.ListInitExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitListInit(ListInitExpression node)
        {
            throw new NotImplementedException();

            //ElementInitNode[] initializers = GetInitializersNodes(node.Initializers);
            //Visit(node.NewExpression);
            //ListInitNode listInitNode = new ListInitNode
            //{
            //    NewExpression = (NewInstanceNode)_currentNode,
            //    Initializers = initializers
            //};
            //_currentNode = listInitNode;

            //return node;
        }

        #endregion // VisitListInit

        #region VisitConditional

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.ConditionalExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitConditional(ConditionalExpression node)
        {
            throw new NotImplementedException();

            //Visit(node.Test);
            //var test = _currentNode;
            //Visit(node.IfTrue);
            //var truePart = _currentNode;
            //Visit(node.IfFalse);
            //var falsePart = _currentNode;
            //_currentNode = new ConditionalNode
            //{
            //    Test = test,
            //    FalsePart = falsePart,
            //    TruePart = truePart
            //};

            //return node;
        }

        #endregion // VisitConditional

        #region VisitInvocation

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.InvocationExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            throw new NotImplementedException();
            //Visit(node.Expression);
            //var expression = _currentNode;
            //ExpressionNodeBase[] args = new ExpressionNodeBase[node.Arguments.Count];
            //for (int index = 0; index < args.Length; index++)
            //{
            //    Visit(node.Arguments[index]);
            //    args[index] = _currentNode;
            //}
            //_currentNode = new InvocationNode
            //{
            //    Arguments = args,
            //    TargetExpression = expression
            //};

            //return node;
        }

        #endregion // VisitInvocation

        #endregion // Visitors (override)

       

  


        
    }
}